#include <iostream>
#include <sstream>
#include <ruby.h>
#include "m3g/m3g.hpp"
#include "ruby-m3g.hpp"
using namespace m3g;
using namespace std;

namespace {
    struct ColorAccessor{
        Material* material;
    };
    VALUE rb_cMaterial_ColorAccessor;
}

void ruby_Material_mark (Material* ptr)
{
    vector<Object3D*> objs;
    extract_all_references (ptr, objs);
    for (int i = 0; i < (int)objs.size(); i++) {
        VALUE val_obj = (VALUE)objs[i]->getExportedEntity();
        if (val_obj)
            rb_gc_mark (val_obj);
    }
}

void ruby_Material_free (Material* ptr)
{
    if (valid_ptr((unsigned char*)ptr, sizeof(*ptr))) {
        __TRY__;
        ptr->~Material ();
        __CATCH__;
    }
    ruby_xfree (ptr);
}

VALUE ruby_Material_allocate (VALUE self)
{
    void* p = ruby_xmalloc (sizeof(Material));
    return Data_Wrap_Struct (self, ruby_Material_mark, ruby_Material_free, p);
}

VALUE ruby_Material_initialize (VALUE self)
{
    Material* p;
    Data_Get_Struct (self, Material, p);
    __TRY__;
    new (p) Material;
    __CATCH__;
    p->setExportedEntity ((void*)self);
    return self;
}

VALUE ruby_Material_get_color (VALUE self)
{
    Material* p;
    Data_Get_Struct (self, Material, p);
    ColorAccessor* accessor;
    VALUE      val_accessor = Data_Make_Struct (rb_cMaterial_ColorAccessor, ColorAccessor, 0, -1, accessor);
    accessor->material = p;
    return val_accessor;
}

VALUE ruby_Material_get_shininess (VALUE self)
{
    Material* p;
    Data_Get_Struct (self, Material, p);
    float shininess;
    __TRY__;
    shininess = p->getShininess ();
    __CATCH__;
    return rb_float_new (shininess);
}

VALUE ruby_Material_is_vertex_color_tracking_enabled (VALUE self)
{
    Material* p;
    Data_Get_Struct (self, Material, p);
    bool enabled;
    __TRY__;
    enabled = p->isVertexColorTrackingEnabled ();
    __CATCH__;
    return enabled ? Qtrue : Qfalse;
}

VALUE ruby_Material_set_shininess (VALUE self, VALUE val_shininess)
{
    Material* p;
    Data_Get_Struct (self, Material, p);
    float shininess = NUM2DBL (val_shininess);
    __TRY__;
    p->setShininess (shininess);
    __CATCH__;
    return Qnil;
}

VALUE ruby_Material_set_vertex_color_tracking_enable (VALUE self, VALUE val_enable)
{
    if (val_enable != Qtrue && val_enable != Qfalse) {
        rb_raise (rb_eArgError, "wrong argument type (expected true or false)");
    }

    Material* p;
    Data_Get_Struct (self, Material, p);
    bool enable = (val_enable == Qtrue) ? true : false;
    __TRY__;
    p->setVertexColorTrackingEnable (enable);
    __CATCH__;
    return Qnil;
}

VALUE ruby_Material_print (VALUE self)
{
    Material* p;
    Data_Get_Struct (self, Material, p);
    ostringstream out;
    __TRY__;
    p->print (out);
    __CATCH__;
    return rb_str_new2 (out.str().c_str());
}

/**
 * Material_ColorAccessor
 */
VALUE ruby_Material_ColorAccessor_get_color (VALUE self, VALUE val_target)
{
    ColorAccessor* p;
    Data_Get_Struct (self, ColorAccessor, p);
    int          target = NUM2INT(val_target);
    unsigned int argb;
    __TRY__;
    argb = p->material->getColor (target);
    __CATCH__;
    return UINT2NUM(argb);
}

VALUE ruby_Material_ColorAccessor_set_color (VALUE self, VALUE val_target, VALUE val_color)
{
    ColorAccessor* p;
    Data_Get_Struct (self, ColorAccessor, p);
    int target = NUM2INT(val_target);
    int color  = NUM2UINT(val_color);
    __TRY__;
    p->material->setColor (target, color);
    __CATCH__;
    return Qnil;
}

void register_Material ()
{
    // Material
    rb_cMaterial            = rb_define_class_under (rb_mM3G, "Material",            rb_cObject3D);

    rb_define_const (rb_cMaterial, "AMBIENT",  INT2NUM(Material::AMBIENT));
    rb_define_const (rb_cMaterial, "DIFFUSE",  INT2NUM(Material::DIFFUSE));
    rb_define_const (rb_cMaterial, "EMISSIVE", INT2NUM(Material::EMISSIVE));
    rb_define_const (rb_cMaterial, "SPECULAR", INT2NUM(Material::SPECULAR));

    rb_define_alloc_func     (rb_cMaterial, ruby_Material_allocate);
    rb_define_private_method (rb_cMaterial, "initialize", (VALUE(*)(...))ruby_Material_initialize,0);

    rb_define_method (rb_cMaterial, "color",                          (VALUE(*)(...))ruby_Material_get_color                       , 0);
    rb_define_method (rb_cMaterial, "shininess",                      (VALUE(*)(...))ruby_Material_get_shininess                   , 0);
    rb_define_method (rb_cMaterial, "vertex_color_tracking_enabled?", (VALUE(*)(...))ruby_Material_is_vertex_color_tracking_enabled, 0);
    rb_define_method (rb_cMaterial, "shininess=",                     (VALUE(*)(...))ruby_Material_set_shininess                   , 1);
    rb_define_method (rb_cMaterial, "vertex_color_tracking_enable=",  (VALUE(*)(...))ruby_Material_set_vertex_color_tracking_enable, 1);
    rb_define_method (rb_cMaterial, "to_s",                           (VALUE(*)(...))ruby_Material_print                           , 0);

    // Material_ColorAccessor
    rb_cMaterial_ColorAccessor  = rb_define_class_under (rb_cMaterial, "ColorAccessor", rb_cObject);

    rb_define_method (rb_cMaterial_ColorAccessor, "[]",      (VALUE(*)(...))ruby_Material_ColorAccessor_get_color, 1); 
    rb_define_method (rb_cMaterial_ColorAccessor, "[]=",     (VALUE(*)(...))ruby_Material_ColorAccessor_set_color, 2); 
}
